﻿/*!
 * Outlayer v1.3.0
 * the brains and guts of a layout library
 * MIT license
 */

(function (window) {

    'use strict';

    // ----- vars ----- //

    var document = window.document;
    var console = window.console;
    var jQuery = window.jQuery;
    var noop = function () { };

    // -------------------------- helpers -------------------------- //

    // extend objects
    function extend(a, b) {
        for (var prop in b) {
            a[prop] = b[prop];
        }
        return a;
    }


    var objToString = Object.prototype.toString;
    function isArray(obj) {
        return objToString.call(obj) === '[object Array]';
    }

    // turn element or nodeList into an array
    function makeArray(obj) {
        var ary = [];
        if (isArray(obj)) {
            // use object if already an array
            ary = obj;
        } else if (obj && typeof obj.length === 'number') {
            // convert nodeList to array
            for (var i = 0, len = obj.length; i < len; i++) {
                ary.push(obj[i]);
            }
        } else {
            // array of single index
            ary.push(obj);
        }
        return ary;
    }

    // http://stackoverflow.com/a/384380/182183
    var isElement = (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') ?
      function isElementDOM2(obj) {
          return obj instanceof HTMLElement;
      } :
      function isElementQuirky(obj) {
          return obj && typeof obj === 'object' &&
            obj.nodeType === 1 && typeof obj.nodeName === 'string';
      };

    // index of helper cause IE8
    var indexOf = Array.prototype.indexOf ? function (ary, obj) {
        return ary.indexOf(obj);
    } : function (ary, obj) {
        for (var i = 0, len = ary.length; i < len; i++) {
            if (ary[i] === obj) {
                return i;
            }
        }
        return -1;
    };

    function removeFrom(obj, ary) {
        var index = indexOf(ary, obj);
        if (index !== -1) {
            ary.splice(index, 1);
        }
    }

    // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
    function toDashed(str) {
        return str.replace(/(.)([A-Z])/g, function (match, $1, $2) {
            return $1 + '-' + $2;
        }).toLowerCase();
    }


    function outlayerDefinition(eventie, docReady, EventEmitter, getSize, matchesSelector, Item) {

        // -------------------------- Outlayer -------------------------- //

        // globally unique identifiers
        var GUID = 0;
        // internal store of all Outlayer intances
        var instances = {};


        /**
         * @param {Element, String} element
         * @param {Object} options
         * @constructor
         */
        function Outlayer(element, options) {
            // use element as selector string
            if (typeof element === 'string') {
                element = document.querySelector(element);
            }

            // bail out if not proper element
            if (!element || !isElement(element)) {
                if (console) {
                    console.error('Bad ' + this.constructor.namespace + ' element: ' + element);
                }
                return;
            }

            this.element = element;

            // options
            this.options = extend({}, this.constructor.defaults);
            this.option(options);

            // add id for Outlayer.getFromElement
            var id = ++GUID;
            this.element.outlayerGUID = id; // expando
            instances[id] = this; // associate via id

            // kick it off
            this._create();

            if (this.options.isInitLayout) {
                this.layout();
            }
        }

        // settings are for internal use only
        Outlayer.namespace = 'outlayer';
        Outlayer.Item = Item;

        // default options
        Outlayer.defaults = {
            containerStyle: {
                position: 'relative'
            },
            isInitLayout: true,
            isOriginLeft: true,
            isOriginTop: true,
            isResizeBound: true,
            isResizingContainer: true,
            // item options
            transitionDuration: '0.4s',
            hiddenStyle: {
                opacity: 0,
                transform: 'scale(0.001)'
            },
            visibleStyle: {
                opacity: 1,
                transform: 'scale(1)'
            }
        };

        // inherit EventEmitter
        extend(Outlayer.prototype, EventEmitter.prototype);

        /**
         * set options
         * @param {Object} opts
         */
        Outlayer.prototype.option = function (opts) {
            extend(this.options, opts);
        };

        Outlayer.prototype._create = function () {
            // get items from children
            this.reloadItems();
            // elements that affect layout, but are not laid out
            this.stamps = [];
            this.stamp(this.options.stamp);
            // set container style
            extend(this.element.style, this.options.containerStyle);

            // bind resize method
            if (this.options.isResizeBound) {
                this.bindResize();
            }
        };

        // goes through all children again and gets bricks in proper order
        Outlayer.prototype.reloadItems = function () {
            // collection of item elements
            this.items = this._itemize(this.element.children);
        };


        /**
         * turn elements into Outlayer.Items to be used in layout
         * @param {Array or NodeList or HTMLElement} elems
         * @returns {Array} items - collection of new Outlayer Items
         */
        Outlayer.prototype._itemize = function (elems) {

            var itemElems = this._filterFindItemElements(elems);
            var Item = this.constructor.Item;

            // create new Outlayer Items for collection
            var items = [];
            for (var i = 0, len = itemElems.length; i < len; i++) {
                var elem = itemElems[i];
                var item = new Item(elem, this);
                items.push(item);
            }

            return items;
        };

        /**
         * get item elements to be used in layout
         * @param {Array or NodeList or HTMLElement} elems
         * @returns {Array} items - item elements
         */
        Outlayer.prototype._filterFindItemElements = function (elems) {
            // make array of elems
            elems = makeArray(elems);
            var itemSelector = this.options.itemSelector;
            var itemElems = [];

            for (var i = 0, len = elems.length; i < len; i++) {
                var elem = elems[i];
                // check that elem is an actual element
                if (!isElement(elem)) {
                    continue;
                }
                // filter & find items if we have an item selector
                if (itemSelector) {
                    // filter siblings
                    if (matchesSelector(elem, itemSelector)) {
                        itemElems.push(elem);
                    }
                    // find children
                    var childElems = elem.querySelectorAll(itemSelector);
                    // concat childElems to filterFound array
                    for (var j = 0, jLen = childElems.length; j < jLen; j++) {
                        itemElems.push(childElems[j]);
                    }
                } else {
                    itemElems.push(elem);
                }
            }

            return itemElems;
        };

        /**
         * getter method for getting item elements
         * @returns {Array} elems - collection of item elements
         */
        Outlayer.prototype.getItemElements = function () {
            var elems = [];
            for (var i = 0, len = this.items.length; i < len; i++) {
                elems.push(this.items[i].element);
            }
            return elems;
        };

        // ----- init & layout ----- //

        /**
         * lays out all items
         */
        Outlayer.prototype.layout = function () {
            this._resetLayout();
            this._manageStamps();

            // don't animate first layout
            var isInstant = this.options.isLayoutInstant !== undefined ?
              this.options.isLayoutInstant : !this._isLayoutInited;
            this.layoutItems(this.items, isInstant);

            // flag for initalized
            this._isLayoutInited = true;
        };

        // _init is alias for layout
        Outlayer.prototype._init = Outlayer.prototype.layout;

        /**
         * logic before any new layout
         */
        Outlayer.prototype._resetLayout = function () {
            this.getSize();
        };


        Outlayer.prototype.getSize = function () {
            this.size = getSize(this.element);
        };

        /**
         * get measurement from option, for columnWidth, rowHeight, gutter
         * if option is String -> get element from selector string, & get size of element
         * if option is Element -> get size of element
         * else use option as a number
         *
         * @param {String} measurement
         * @param {String} size - width or height
         * @private
         */
        Outlayer.prototype._getMeasurement = function (measurement, size) {
            var option = this.options[measurement];
            var elem;
            if (!option) {
                // default to 0
                this[measurement] = 0;
            } else {
                // use option as an element
                if (typeof option === 'string') {
                    elem = this.element.querySelector(option);
                } else if (isElement(option)) {
                    elem = option;
                }
                // use size of element, if element
                this[measurement] = elem ? getSize(elem)[size] : option;
            }
        };

        /**
         * layout a collection of item elements
         * @api public
         */
        Outlayer.prototype.layoutItems = function (items, isInstant) {
            items = this._getItemsForLayout(items);

            this._layoutItems(items, isInstant);

            this._postLayout();
        };

        /**
         * get the items to be laid out
         * you may want to skip over some items
         * @param {Array} items
         * @returns {Array} items
         */
        Outlayer.prototype._getItemsForLayout = function (items) {
            var layoutItems = [];
            for (var i = 0, len = items.length; i < len; i++) {
                var item = items[i];
                if (!item.isIgnored) {
                    layoutItems.push(item);
                }
            }
            return layoutItems;
        };

        /**
         * layout items
         * @param {Array} items
         * @param {Boolean} isInstant
         */
        Outlayer.prototype._layoutItems = function (items, isInstant) {
            var _this = this;
            function onItemsLayout() {
                _this.emitEvent('layoutComplete', [_this, items]);
            }

            if (!items || !items.length) {
                // no items, emit event with empty array
                onItemsLayout();
                return;
            }

            // emit layoutComplete when done
            this._itemsOn(items, 'layout', onItemsLayout);

            var queue = [];

            for (var i = 0, len = items.length; i < len; i++) {
                var item = items[i];
                // get x/y object from method
                var position = this._getItemLayoutPosition(item);
                // enqueue
                position.item = item;
                position.isInstant = isInstant || item.isLayoutInstant;
                queue.push(position);
            }

            this._processLayoutQueue(queue);
        };

        /**
         * get item layout position
         * @param {Outlayer.Item} item
         * @returns {Object} x and y position
         */
        Outlayer.prototype._getItemLayoutPosition = function ( /* item */) {
            return {
                x: 0,
                y: 0
            };
        };

        /**
         * iterate over array and position each item
         * Reason being - separating this logic prevents 'layout invalidation'
         * thx @paul_irish
         * @param {Array} queue
         */
        Outlayer.prototype._processLayoutQueue = function (queue) {
            for (var i = 0, len = queue.length; i < len; i++) {
                var obj = queue[i];
                this._positionItem(obj.item, obj.x, obj.y, obj.isInstant);
            }
        };

        /**
         * Sets position of item in DOM
         * @param {Outlayer.Item} item
         * @param {Number} x - horizontal position
         * @param {Number} y - vertical position
         * @param {Boolean} isInstant - disables transitions
         */
        Outlayer.prototype._positionItem = function (item, x, y, isInstant) {
            if (isInstant) {
                // if not transition, just set CSS
                item.goTo(x, y);
            } else {
                item.moveTo(x, y);
            }
        };

        /**
         * Any logic you want to do after each layout,
         * i.e. size the container
         */
        Outlayer.prototype._postLayout = function () {
            this.resizeContainer();
        };

        Outlayer.prototype.resizeContainer = function () {
            if (!this.options.isResizingContainer) {
                return;
            }
            var size = this._getContainerSize();
            if (size) {
                this._setContainerMeasure(size.width, true);
                this._setContainerMeasure(size.height, false);
            }
        };

        /**
         * Sets width or height of container if returned
         * @returns {Object} size
         *   @param {Number} width
         *   @param {Number} height
         */
        Outlayer.prototype._getContainerSize = noop;

        /**
         * @param {Number} measure - size of width or height
         * @param {Boolean} isWidth
         */
        Outlayer.prototype._setContainerMeasure = function (measure, isWidth) {
            if (measure === undefined) {
                return;
            }

            var elemSize = this.size;
            // add padding and border width if border box
            if (elemSize.isBorderBox) {
                measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
                  elemSize.borderLeftWidth + elemSize.borderRightWidth :
                  elemSize.paddingBottom + elemSize.paddingTop +
                  elemSize.borderTopWidth + elemSize.borderBottomWidth;
            }

            measure = Math.max(measure, 0);
            this.element.style[isWidth ? 'width' : 'height'] = measure + 'px';
        };

        /**
         * trigger a callback for a collection of items events
         * @param {Array} items - Outlayer.Items
         * @param {String} eventName
         * @param {Function} callback
         */
        Outlayer.prototype._itemsOn = function (items, eventName, callback) {
            var doneCount = 0;
            var count = items.length;
            // event callback
            var _this = this;
            function tick() {
                doneCount++;
                if (doneCount === count) {
                    callback.call(_this);
                }
                return true; // bind once
            }
            // bind callback
            for (var i = 0, len = items.length; i < len; i++) {
                var item = items[i];
                item.on(eventName, tick);
            }
        };

        // -------------------------- ignore & stamps -------------------------- //


        /**
         * keep item in collection, but do not lay it out
         * ignored items do not get skipped in layout
         * @param {Element} elem
         */
        Outlayer.prototype.ignore = function (elem) {
            var item = this.getItem(elem);
            if (item) {
                item.isIgnored = true;
            }
        };

        /**
         * return item to layout collection
         * @param {Element} elem
         */
        Outlayer.prototype.unignore = function (elem) {
            var item = this.getItem(elem);
            if (item) {
                delete item.isIgnored;
            }
        };

        /**
         * adds elements to stamps
         * @param {NodeList, Array, Element, or String} elems
         */
        Outlayer.prototype.stamp = function (elems) {
            elems = this._find(elems);
            if (!elems) {
                return;
            }

            this.stamps = this.stamps.concat(elems);
            // ignore
            for (var i = 0, len = elems.length; i < len; i++) {
                var elem = elems[i];
                this.ignore(elem);
            }
        };

        /**
         * removes elements to stamps
         * @param {NodeList, Array, or Element} elems
         */
        Outlayer.prototype.unstamp = function (elems) {
            elems = this._find(elems);
            if (!elems) {
                return;
            }

            for (var i = 0, len = elems.length; i < len; i++) {
                var elem = elems[i];
                // filter out removed stamp elements
                removeFrom(elem, this.stamps);
                this.unignore(elem);
            }

        };

        /**
         * finds child elements
         * @param {NodeList, Array, Element, or String} elems
         * @returns {Array} elems
         */
        Outlayer.prototype._find = function (elems) {
            if (!elems) {
                return;
            }
            // if string, use argument as selector string
            if (typeof elems === 'string') {
                elems = this.element.querySelectorAll(elems);
            }
            elems = makeArray(elems);
            return elems;
        };

        Outlayer.prototype._manageStamps = function () {
            if (!this.stamps || !this.stamps.length) {
                return;
            }

            this._getBoundingRect();

            for (var i = 0, len = this.stamps.length; i < len; i++) {
                var stamp = this.stamps[i];
                this._manageStamp(stamp);
            }
        };

        // update boundingLeft / Top
        Outlayer.prototype._getBoundingRect = function () {
            // get bounding rect for container element
            var boundingRect = this.element.getBoundingClientRect();
            var size = this.size;
            this._boundingRect = {
                left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
                top: boundingRect.top + size.paddingTop + size.borderTopWidth,
                right: boundingRect.right - (size.paddingRight + size.borderRightWidth),
                bottom: boundingRect.bottom - (size.paddingBottom + size.borderBottomWidth)
            };
        };

        /**
         * @param {Element} stamp
        **/
        Outlayer.prototype._manageStamp = noop;

        /**
         * get x/y position of element relative to container element
         * @param {Element} elem
         * @returns {Object} offset - has left, top, right, bottom
         */
        Outlayer.prototype._getElementOffset = function (elem) {
            var boundingRect = elem.getBoundingClientRect();
            var thisRect = this._boundingRect;
            var size = getSize(elem);
            var offset = {
                left: boundingRect.left - thisRect.left - size.marginLeft,
                top: boundingRect.top - thisRect.top - size.marginTop,
                right: thisRect.right - boundingRect.right - size.marginRight,
                bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
            };
            return offset;
        };

        // -------------------------- resize -------------------------- //

        // enable event handlers for listeners
        // i.e. resize -> onresize
        Outlayer.prototype.handleEvent = function (event) {
            var method = 'on' + event.type;
            if (this[method]) {
                this[method](event);
            }
        };

        /**
         * Bind layout to window resizing
         */
        Outlayer.prototype.bindResize = function () {
            // bind just one listener
            if (this.isResizeBound) {
                return;
            }
            eventie.bind(window, 'resize', this);
            this.isResizeBound = true;
        };

        /**
         * Unbind layout to window resizing
         */
        Outlayer.prototype.unbindResize = function () {
            if (this.isResizeBound) {
                eventie.unbind(window, 'resize', this);
            }
            this.isResizeBound = false;
        };

        // original debounce by John Hann
        // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/

        // this fires every resize
        Outlayer.prototype.onresize = function () {
            if (this.resizeTimeout) {
                clearTimeout(this.resizeTimeout);
            }

            var _this = this;
            function delayed() {
                _this.resize();
                delete _this.resizeTimeout;
            }

            this.resizeTimeout = setTimeout(delayed, 100);
        };

        // debounced, layout on resize
        Outlayer.prototype.resize = function () {
            // don't trigger if size did not change
            // or if resize was unbound. See #9
            if (!this.isResizeBound || !this.needsResizeLayout()) {
                return;
            }

            this.layout();
        };

        /**
         * check if layout is needed post layout
         * @returns Boolean
         */
        Outlayer.prototype.needsResizeLayout = function () {
            var size = getSize(this.element);
            // check that this.size and size are there
            // IE8 triggers resize on body size change, so they might not be
            var hasSizes = this.size && size;
            return hasSizes && size.innerWidth !== this.size.innerWidth;
        };

        // -------------------------- methods -------------------------- //

        /**
         * add items to Outlayer instance
         * @param {Array or NodeList or Element} elems
         * @returns {Array} items - Outlayer.Items
        **/
        Outlayer.prototype.addItems = function (elems) {
            var items = this._itemize(elems);
            // add items to collection
            if (items.length) {
                this.items = this.items.concat(items);
            }
            return items;
        };

        /**
         * Layout newly-appended item elements
         * @param {Array or NodeList or Element} elems
         */
        Outlayer.prototype.appended = function (elems) {
            var items = this.addItems(elems);
            if (!items.length) {
                return;
            }
            // layout and reveal just the new items
            this.layoutItems(items, true);
            this.reveal(items);
        };

        /**
         * Layout prepended elements
         * @param {Array or NodeList or Element} elems
         */
        Outlayer.prototype.prepended = function (elems) {
            var items = this._itemize(elems);
            if (!items.length) {
                return;
            }
            // add items to beginning of collection
            var previousItems = this.items.slice(0);
            this.items = items.concat(previousItems);
            // start new layout
            this._resetLayout();
            this._manageStamps();
            // layout new stuff without transition
            this.layoutItems(items, true);
            this.reveal(items);
            // layout previous items
            this.layoutItems(previousItems);
        };

        /**
         * reveal a collection of items
         * @param {Array of Outlayer.Items} items
         */
        Outlayer.prototype.reveal = function (items) {
            var len = items && items.length;
            if (!len) {
                return;
            }
            for (var i = 0; i < len; i++) {
                var item = items[i];
                item.reveal();
            }
        };

        /**
         * hide a collection of items
         * @param {Array of Outlayer.Items} items
         */
        Outlayer.prototype.hide = function (items) {
            var len = items && items.length;
            if (!len) {
                return;
            }
            for (var i = 0; i < len; i++) {
                var item = items[i];
                item.hide();
            }
        };

        /**
         * get Outlayer.Item, given an Element
         * @param {Element} elem
         * @param {Function} callback
         * @returns {Outlayer.Item} item
         */
        Outlayer.prototype.getItem = function (elem) {
            // loop through items to get the one that matches
            for (var i = 0, len = this.items.length; i < len; i++) {
                var item = this.items[i];
                if (item.element === elem) {
                    // return item
                    return item;
                }
            }
        };

        /**
         * get collection of Outlayer.Items, given Elements
         * @param {Array} elems
         * @returns {Array} items - Outlayer.Items
         */
        Outlayer.prototype.getItems = function (elems) {
            if (!elems || !elems.length) {
                return;
            }
            var items = [];
            for (var i = 0, len = elems.length; i < len; i++) {
                var elem = elems[i];
                var item = this.getItem(elem);
                if (item) {
                    items.push(item);
                }
            }

            return items;
        };

        /**
         * remove element(s) from instance and DOM
         * @param {Array or NodeList or Element} elems
         */
        Outlayer.prototype.remove = function (elems) {
            elems = makeArray(elems);

            var removeItems = this.getItems(elems);
            // bail if no items to remove
            if (!removeItems || !removeItems.length) {
                return;
            }

            this._itemsOn(removeItems, 'remove', function () {
                this.emitEvent('removeComplete', [this, removeItems]);
            });

            for (var i = 0, len = removeItems.length; i < len; i++) {
                var item = removeItems[i];
                item.remove();
                // remove item from collection
                removeFrom(item, this.items);
            }
        };

        // ----- destroy ----- //

        // remove and disable Outlayer instance
        Outlayer.prototype.destroy = function () {
            // clean up dynamic styles
            var style = this.element.style;
            style.height = '';
            style.position = '';
            style.width = '';
            // destroy items
            for (var i = 0, len = this.items.length; i < len; i++) {
                var item = this.items[i];
                item.destroy();
            }

            this.unbindResize();

            var id = this.element.outlayerGUID;
            delete instances[id]; // remove reference to instance by id
            delete this.element.outlayerGUID;
            // remove data for jQuery
            if (jQuery) {
                jQuery.removeData(this.element, this.constructor.namespace);
            }

        };

        // -------------------------- data -------------------------- //

        /**
         * get Outlayer instance from element
         * @param {Element} elem
         * @returns {Outlayer}
         */
        Outlayer.data = function (elem) {
            var id = elem && elem.outlayerGUID;
            return id && instances[id];
        };


        // -------------------------- create Outlayer class -------------------------- //

        /**
         * create a layout class
         * @param {String} namespace
         */
        Outlayer.create = function (namespace, options) {
            // sub-class Outlayer
            function Layout() {
                Outlayer.apply(this, arguments);
            }
            // inherit Outlayer prototype, use Object.create if there
            if (Object.create) {
                Layout.prototype = Object.create(Outlayer.prototype);
            } else {
                extend(Layout.prototype, Outlayer.prototype);
            }
            // set contructor, used for namespace and Item
            Layout.prototype.constructor = Layout;

            Layout.defaults = extend({}, Outlayer.defaults);
            // apply new options
            extend(Layout.defaults, options);
            // keep prototype.settings for backwards compatibility (Packery v1.2.0)
            Layout.prototype.settings = {};

            Layout.namespace = namespace;

            Layout.data = Outlayer.data;

            // sub-class Item
            Layout.Item = function LayoutItem() {
                Item.apply(this, arguments);
            };

            Layout.Item.prototype = new Item();

            // -------------------------- declarative -------------------------- //

            /**
             * allow user to initialize Outlayer via .js-namespace class
             * options are parsed from data-namespace-option attribute
             */
            docReady(function () {
                var dashedNamespace = toDashed(namespace);
                var elems = document.querySelectorAll('.js-' + dashedNamespace);
                var dataAttr = 'data-' + dashedNamespace + '-options';

                for (var i = 0, len = elems.length; i < len; i++) {
                    var elem = elems[i];
                    var attr = elem.getAttribute(dataAttr);
                    var options;
                    try {
                        options = attr && JSON.parse(attr);
                    } catch (error) {
                        // log error, do not initialize
                        if (console) {
                            console.error('Error parsing ' + dataAttr + ' on ' +
                              elem.nodeName.toLowerCase() + (elem.id ? '#' + elem.id : '') + ': ' +
                              error);
                        }
                        continue;
                    }
                    // initialize
                    var instance = new Layout(elem, options);
                    // make available via $().data('layoutname')
                    if (jQuery) {
                        jQuery.data(elem, namespace, instance);
                    }
                }
            });

            // -------------------------- jQuery bridge -------------------------- //

            // make into jQuery plugin
            if (jQuery && jQuery.bridget) {
                jQuery.bridget(namespace, Layout);
            }

            return Layout;
        };

        // ----- fin ----- //

        // back in global
        Outlayer.Item = Item;

        return Outlayer;

    }

    // -------------------------- transport -------------------------- //

    if (typeof define === 'function' && define.amd) {
        // AMD
        define([
            'eventie/eventie',
            'doc-ready/doc-ready',
            'eventEmitter/EventEmitter',
            'get-size/get-size',
            'matches-selector/matches-selector',
            './item'
        ],
          outlayerDefinition);
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = outlayerDefinition(
          require('eventie'),
          require('doc-ready'),
          require('wolfy87-eventemitter'),
          require('get-size'),
          require('desandro-matches-selector'),
          require('./item')
        );
    } else {
        // browser global
        window.Outlayer = outlayerDefinition(
          window.eventie,
          window.docReady,
          window.EventEmitter,
          window.getSize,
          window.matchesSelector,
          window.Outlayer.Item
        );
    }

})(window);
